package heman.server.ipc;


import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import ks.framework.common.Message;
import ks.framework.common.network.CommunicationAgent;
import ks.framework.common.network.SimpleServerConnection;
import ks.framework.debug.Debug;

/**
 * The Listener class is responsible for processing all Client requests. Each
 * request is a serialized <code>Command</code> object which is read from the
 * client socket connection.
 * <p>
 * Differentiates between administrator login (or normal) by means
 * of 'setAdminOnly'.
 * 
 * @author George T. Heineman (heineman@cs.wpi.edu)
 */
public class Listener extends SimpleServerConnection {
	/** Knows of logout command. */
	public static final String logoutRequest = "logout";

	/** Knows of login command. */
	public static final String loginRequest = "login";

	/** Knows of player/password attributes. */
	public static final String playerAttribute = "player";
	public static final String passwordAttribute = "password";
	public static final String selfRegisterAttribute = "self-register";

	/** Know who the server is to process all messages. */
	protected Server server;

	/** Name of user connecting from the client. */
	private String who = null;

	/** Communication port. */
	private int port = 7878;

	/** Default Communication port. */
	public static final int defaultPort = 7878;

	/**
	 * Default Listener constructed for given Server.
	 * 
	 * @param cs     Server
	 */
	public Listener(Server cs) {
		this(cs, defaultPort);
	}

	/**
	 * Listener constructed for given Server on given port.
	 * 
	 * @param cs    Server to process requests
	 * @param port  port on which to listen
	 */
	public Listener(Server cs, int port) {
		server = cs;
		this.port = port;
	}

	/**
	 * All communication from a client occurs within the context of a Listener
	 * thread (which we call a session.)
	 * <p>
	 * Responsible for processing logoutCommands and severing the connection.
	 * 
	 * @param agent   Communication Agent for this session.
	 */
	public void handleSession(CommunicationAgent agent) {
		Message m = (Message) agent.readObject();

		// client has already disconnected.
		if (m == null) return;

		// the first one in must be the login command
		if (!m.isRequest()) { return; }
		String name = m.getName();
		if (!name.equalsIgnoreCase(loginRequest)) { return; }

		// must retrieve player/password.
		Node login = m.contents();
		NamedNodeMap nnp = login.getAttributes();
		Node player = nnp.getNamedItem(playerAttribute);
		Node password = nnp.getNamedItem(passwordAttribute);
		Node selfRegister = nnp.getNamedItem(selfRegisterAttribute);

		// self registration no longer needs to send user/password.
		boolean self = Boolean.valueOf(selfRegister.getNodeValue()).booleanValue();

		if (self) {
			// try to self-register an account. 
			who = server.selfRegister(password.getNodeValue());
			if (who == null) {
				agent.writeObject(loginFailure(m.id, "", "Unable to self-register user."));
				return;
			}
		} else {
			if (player == null || password == null) { return; }
			who = player.getNodeValue();
			String hashPass = password.getNodeValue();

			// check that user is authorized.
			try {
				if (!server.authorize(who, hashPass)) {
					agent.writeObject(loginFailure(m.id, who, "Unable to authorize user to login"));
					return;
				}
			} catch (Exception e) {
				// failed for specific reason.
				agent.writeObject(loginFailure(m.id, who, e.getMessage()));
				return;
			}
		}

		// Record persistent connection to this user.
		server.connectUser(who, agent);

		// Corroborate with ack
		Message ack = Listener.loginSuccess(m.id, who, "accepted");
		if (!agent.writeObject(ack)) {
			System.err.println("Unable to corroborate login with client.");
			server.disconnectUser(who);
			return;
		}

		// pass along login message.
		server.process(who, m);
		server.login(server.com, m);
		
		// Enter Main Loop to retrieve information from these clients
		boolean cleanLogout = false;
		try {
			Message incoming;
			while ((incoming = (Message) agent.readObject()) != null) {

				// only an exception can break us out of here, or receipt
				// of a logout message.
				server.process(who, incoming);

				// if a logout was issued from our client, shut down its communication.
				if (incoming.getName().equalsIgnoreCase(logoutRequest)) {
					cleanLogout = true;
					server.logout(server.com, incoming);
					break;
				}
			}
		} catch (RuntimeException re) {
			re.printStackTrace();
		}

		// deal with logout as required....
		if (!cleanLogout) {
			System.out.println("Generating Logout for '" + who + "'");
			String cmd = Message.requestHeader() + "<logout/></request>";
			Document d = Message.construct(cmd);
			Message logoutMessage = new Message(d);
			logoutMessage.setOriginator(who);
			
			server.process(who, logoutMessage);
			if (server.com != null) {
				server.logout(server.com, logoutMessage);
			} else {
				System.err.println("Communicator shutdown. No ability to send messages.");
			}
		}

		Debug.println(who + " has disconnected.");
		server.disconnectUser(who);
	}

	
	/**
	 * Generate login failure message.
	 * <p>
	 * @param m        command which succeeded.
	 * @param reason   failure reason.
	 */
	public static Message loginFailure(String id, String who, String reason) {
		// produce request.
		StringBuilder sb = new StringBuilder("<response version='1.0' id='" + id + "' success='false' reason='" + reason + "'>");
		sb.append("<loginResponse player='" + who + "'/></response>");
		
		Document d = Message.construct(sb.toString());

		Message r = new Message (d);
		return r;
	}

	/**
	 * Generate login success message.
	 * <p>
	 * @param m        command which succeeded.
	 * @param reason   failure reason.
	 */
	public static Message loginSuccess(String id, String who, String reason) {
		// produce request.
		StringBuilder sb = new StringBuilder("<response version='1.0' id='" + id + "' success='true' reason='" + reason + "'>");
		sb.append("<loginResponse player='" + who + "'/></response>");
		
		Document d = Message.construct(sb.toString());
		System.out.println(sb.toString());
		Message r = new Message (d);
		return r;
	}
	
	/**
	 * Create listener on the prepared port.
	 */
	public boolean start() {
		Debug.println("Accepting calls on " + port);
		return super.start(port);
	}
}